﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel.Composition;
using System.Linq;
using System.Xml;
using System.Xml.Linq;
using System.Xml.XPath;

namespace AlbumArtOrganizer.Plugins.Bing.ImageFinder
{
    [Export(typeof(AlbumArtOrganizer.ImageFinder.IImageFinder))]
    public class ImageFinder : AlbumArtOrganizer.ImageFinder.ImageFinder, AlbumArtOrganizer.ImageFinder.IImageFinder
    {
        public const string OPTIONCONTRACTNAME = "ImageFinderBingOption";
        Dictionary<string, ImageFinderResults> searchCache = null;

        public ImageFinder()
        {
            searchCache = new Dictionary<string, ImageFinderResults>();
        }

        public override string Name
        {
            get
            {
                return "Bing";
            }
            set
            {
            }
        }

        public override bool IsSearchAvailable
        {
            get
            {
                if (CurrentAlbum == null)
                {
                    return false;
                }
                var searchString = GetSearchString(CurrentAlbum.Artist.Name, CurrentAlbum.Title);
                if (!searchCache.ContainsKey(searchString))
                {
                    return true;
                }
                else
                {
                    return searchCache[searchString].Images.Count() < searchCache[searchString].TotalResultsCount;
                }
            }
            set
            {
            }
        }

        AlbumArtOrganizer.Model.IAlbum _currentAlbum = null;
        AlbumArtOrganizer.Model.IAlbum _previousAlbum = null;
        public override AlbumArtOrganizer.Model.IAlbum CurrentAlbum
        {
            get
            {
                return _currentAlbum;
            }
            set
            {
                _previousAlbum = _currentAlbum;
                _currentAlbum = value;
                OnPropertyChanged("CurrentAlbum");
                OnPropertyChanged("IsSearchAvailable");
            }
        }

        [ImportMany(OPTIONCONTRACTNAME, typeof(AlbumArtOrganizer.Model.IOption))]
        public override ObservableCollection<AlbumArtOrganizer.Model.IOption> Options
        {
            get;
            set;
        }

        public override string GetSearchString(string artistName, string albumTitle)
        {
            var optionSearchFormatString = Options.FirstOrDefault(option => option is Model.OptionSearchFormatString) as Model.OptionSearchFormatString;
            if (optionSearchFormatString != null)
            {
                return String.Format(optionSearchFormatString.Value, artistName, albumTitle);
            }
            else
            {
                return String.Format("\"{0}\" \"{1}\" cover", artistName, albumTitle);
            }
        }

        public override AlbumArtOrganizer.ImageFinder.IImageFinderResults FindImages(string artistName, string albumTitle)
        {
            var searchString = GetSearchString(artistName, albumTitle);
            var resultCount = 25;
            var offset = 0;
            if (searchCache.ContainsKey(searchString))
            {
                var imageFinderResults = searchCache[searchString];
                offset = imageFinderResults.Images.Count();
            }
            var requestURI = GetRequestUri(searchString, resultCount, offset);
            var xmlTuple = ReadXml(requestURI);
            var xDocument = xmlTuple.Item1;
            var xmlNamespaceManager = xmlTuple.Item2;
            xmlNamespaceManager.AddNamespace("mms", "http://schemas.microsoft.com/LiveSearch/2008/04/XML/multimedia");
            var xElemRoot = xDocument.Root;
            var xElemImage = xElemRoot.XPathSelectElement("mms:Image", xmlNamespaceManager);
            var xElemResults = xElemImage.XPathSelectElements("./mms:Results/mms:ImageResult", xmlNamespaceManager);

            var imageResults = new ImageFinderResults()
            {
                TotalResultsCount = Int32.Parse(xElemImage.XPathSelectElement("./mms:Total", xmlNamespaceManager).Value),
                Offset = Int32.Parse(xElemImage.XPathSelectElement("./mms:Offset", xmlNamespaceManager).Value),
                Images = from xElem in xElemResults
                         select new Image()
                         {
                             ThumbnailLocation = new Uri(xElem.XPathSelectElement("./mms:Thumbnail/mms:Url", xmlNamespaceManager).Value),
                             FullSizeLocation = new Uri(xElem.XPathSelectElement("./mms:MediaUrl", xmlNamespaceManager).Value),
                             FullSizeWidth = Int32.Parse(xElem.XPathSelectElement("./mms:Width", xmlNamespaceManager).Value),
                             FullSizeHeight = Int32.Parse(xElem.XPathSelectElement("./mms:Height", xmlNamespaceManager).Value)
                         }
            };
            if (searchCache.ContainsKey(searchString))
            {
                var imageFinderResults = searchCache[searchString];
                imageFinderResults.TotalResultsCount = imageResults.TotalResultsCount;
                imageFinderResults.Offset = imageResults.Offset;
                imageFinderResults.Images = imageFinderResults.Images.Concat(imageResults.Images);
            }
            else
            {
                searchCache[searchString] = imageResults;
            }

            return imageResults;
        }

        string AppId = "E30737EFBA773B0A5710A9D0898D2D3C8D5CC5AC";

        string GetRequestUri(string searchString, int resultCount, int offset)
        {
            var sizeFilterString = String.Empty;
            var optionImageSize = Options.FirstOrDefault(option => option is Model.OptionImageSize) as Model.OptionImageSize;
            if (optionImageSize != null)
            {
                var sizeValue = optionImageSize.GetSearchValue();
                sizeFilterString = "&Image.Filters=Size:" + sizeValue;
            }
            var adultString = String.Empty;
            var optionSafeSearch = Options.FirstOrDefault(option => option is Model.OptionSafeSearch) as Model.OptionSafeSearch;
            if (optionSafeSearch != null)
            {
                var safeValue = optionSafeSearch.GetSearchString();
                adultString = "&Adult=" + safeValue;
            }

            var uriBuilder = new UriBuilder(String.Format("http://api.search.live.net/xml.aspx?AppId={0}&Sources=Image&Version=2.0&Market=en-us", AppId));
            // Manually encoding &, ? and =
            uriBuilder.Query = uriBuilder.Query.Substring(1) + "&Query=" + searchString.Replace("&", "%26")
                                                                                       .Replace("?", "%3F")
                                                                                       .Replace("=", "%3D");
            uriBuilder.Query = uriBuilder.Query.Substring(1) + adultString;
            uriBuilder.Query = uriBuilder.Query.Substring(1) + "&Image.Count=" + resultCount;
            uriBuilder.Query = uriBuilder.Query.Substring(1) + "&Image.Offset=" + offset;
            uriBuilder.Query = uriBuilder.Query.Substring(1) + sizeFilterString;
            var requestString = uriBuilder.Uri.AbsoluteUri;

            return requestString;
        }

        Tuple<XDocument, XmlNamespaceManager> ReadXml(string requestURI)
        {
            var xmlReader = GetXmlReader(requestURI);
            var xDocument = GetXDocument(xmlReader);
            var xmlNamespaceManager = GetNamespaceManager(xmlReader);
            return Tuple.Create(xDocument, xmlNamespaceManager);
        }

        XmlReader GetXmlReader(string requestURI)
        {
            var xmlReader = XmlReader.Create(requestURI);
            return xmlReader;
        }

        XmlNamespaceManager GetNamespaceManager(XmlReader xmlReader)
        {
            var xmlNamespaceManager = new XmlNamespaceManager(xmlReader.NameTable);
            return xmlNamespaceManager;
        }

        XDocument GetXDocument(XmlReader xmlReader)
        {
            var xDocument = XDocument.Load(xmlReader);
            return xDocument;
        }
    }
}
